package net.cyndeline.scalarlib.rldungeon.dgs.strategy.help

import scalax.collection.immutable.Graph
import net.cyndeline.scalarlib.rlgraph.util.VertexReplacer
import net.cyndeline.scalarlib.rldungeon.dgs.graph.Room

/**
 * Replaces represented vertices in a super-graph by finding which collapsed node that stores the vertex. If the
 * node is a super-node, edges connected to it will have their original targets replaced as well.
 *
 * Finding the node representing the vertex requires O(n) time.
 *
 * @constructor Constructs a new vertex replacer for super-graphs.
 */
class SuperGraphVertexReplacer extends SuperGraphVertexReplacerInterface {

  /**
   * Locate a single vertex inside a collapsed node, replace it with another vertex and replace every
   * original target reference inside edges connecting to that node as well.
   *
   * @param vertexToReplace Old vertex to replace.
   * @param replaceWith New vertex to replace the old vertex with.
   * @param graph Collapsed graph with the node containing the old vertex.
   * @tparam RepresentedType Type of node being replaced.
   * @return A new collapsed graph with the old vertex replaced, as well as an instance of the updated collapsed node
   *         that the original vertex belonged to and the old node.
   */
  def replaceVertex[RepresentedType <: Room[RepresentedType]]
              (vertexToReplace: RepresentedType,
               replaceWith: RepresentedType,
               graph: Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner]
              ): (Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner], CollapsedNode[RepresentedType], CollapsedNode[RepresentedType]) = {

    replaceSet(Map(vertexToReplace -> replaceWith), findRepresentingNode(Set(vertexToReplace), graph), graph)
  }

  /**
   * Replaces multiple vertices inside a single super node.
   *
   * @param replacementMapping A collection that maps every old vertex (currently in the node) to the new copy that
   *                           should replace it.
   * @param graph Collapsed graph with the node containing the old vertices
   * @tparam RepresentedType Type of node being replaced.
   * @return A new collapsed graph with the old vertices replaced, as well as an instance of the updated collapsed node
   *         that the original vertex belonged to and the old node.
   */
  def replaceVertexSet[RepresentedType <: Room[RepresentedType]]
              (replacementMapping: Map[RepresentedType, RepresentedType],
               graph: Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner]
               ): (Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner], CollapsedNode[RepresentedType], CollapsedNode[RepresentedType]) = {
    replaceSet(replacementMapping, findRepresentingNode(replacementMapping.keySet, graph), graph)
  }

  /**
   * Replaces multiple vertices inside a single super node, and also takes the node containing the vertices as data.
   * Removes the need to iterate over the graphs vertices in order to find the correct node.
   * @param replacementMapping A collection that maps every old vertex (currently in the node) to the new copy that
   *                           should replace it.
   * @param node The node containing all vertices in the replacementsmappings keyset.
   * @param graph Collapsed graph with the node containing the old vertices
   * @tparam RepresentedType Type of node being replaced.
   * @return A new collapsed graph with the old vertices replaced, as well as an instance of the updated collapsed node
   *         that the original vertex belonged to.
   */
  def replaceVertexSetUsingNode[RepresentedType <: Room[RepresentedType]]
                                (replacementMapping: Map[RepresentedType, RepresentedType],
                                 node: CollapsedNode[RepresentedType],
                                 graph: Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner]
                                 ): (Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner], CollapsedNode[RepresentedType]) = {
    val result = replaceSet(replacementMapping, node, graph)
    (result._1, result._2)
  }

  private def findRepresentingNode[RepresentedType <: Room[RepresentedType]]
                                  (vertices: Set[RepresentedType],
                                   graph: Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner]): CollapsedNode[RepresentedType] = {
    graph.nodes.find(n => n.containsSubset(vertices)).getOrElse {
      throw new IllegalArgumentException("No node representing the vertices " + vertices + " found in " + graph)
    }
  }

  private def replaceSet[RepresentedType <: Room[RepresentedType]]
                        (vertexReplacement: Map[RepresentedType, RepresentedType],
                         representingNode: CollapsedNode[RepresentedType],
                         graph: Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner]
                         ): (Graph[CollapsedNode[RepresentedType], CollapsedEdge[RepresentedType]#Inner], CollapsedNode[RepresentedType], CollapsedNode[RepresentedType]) = {
    val oldVertices = vertexReplacement.keySet
    var newNode = representingNode
    for (oldAndNew <- vertexReplacement.iterator) {
      newNode = newNode.replace(oldAndNew._1, oldAndNew._2)
    }

    val replacer = new VertexReplacer(CollapsedEdge.factory[RepresentedType])
    var replacedGraph = replacer.replace(representingNode, newNode, graph)

    if (newNode.isSuperNode) {
      val edges = replacedGraph.get(newNode).edges.iterator

      while (edges.hasNext) {
        val edge = edges.next().toOuter
        var edgeNeedsUpdating = false

        val originalFrom =
          if (edge.originalFrom.isDefined && oldVertices.contains(edge.originalFrom.get)) {
            edgeNeedsUpdating = true
            Option(vertexReplacement(edge.originalFrom.get))
          } else {
            edge.originalFrom
          }

        val originalTo =
          if (edge.originalTo.isDefined && oldVertices.contains(edge.originalTo.get)) {
            edgeNeedsUpdating = true
            Option(vertexReplacement(edge.originalTo.get))
          } else {
            edge.originalTo
          }

        if (edgeNeedsUpdating) {
          val newEdge = CollapsedEdge(edge._1, edge._2, originalFrom, originalTo)
          replacedGraph -= edge
          replacedGraph += newEdge
        }
      }
    }

    (replacedGraph, newNode, representingNode)
  }
}
